﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;

namespace Net
{
    /**
        int32 对应 int
        int64 对应 long
        uint32 对应 uint
        uint64 对应 ulong
        sint32 对应 int // 使用zigzag编码
        sint64 对应 long // 使用zigzag编码
        fixed32 对应 uint 
        fixed64 对应 ulong
        sfixed32 对应 int
        sfixed64 对应 long
        bool 对应 bool
        string 对应 string
        bytes 对应 byte[]
        float 对应 float
        double 对应 double
     */
    
    internal class WireType
    {
        public const int Varint = 0;
        public const int Fixed64 = 1;
        public const int LengthDelimited = 2;
        public const int StartGroup = 3;
        public const int EndGroup = 4;
        public const int Fixed32 = 5;
    }
    
    public static class ProtoHelper
    {
        private static Dictionary<string, string> _readers = new Dictionary<string, string>
        {
            {"int32", nameof(ReadInt32)},
            {"int64", nameof(ReadInt64)},
            {"uint32", nameof(ReadUInt32)},
            {"uint64", nameof(ReadUInt64)},
            {"sint32", nameof(ReadSInt32)},
            {"sint64", nameof(ReadSInt64)},
            {"fixed32", nameof(ReadFixed32)},
            {"fixed64", nameof(ReadFixed64)},
            {"sfixed32", nameof(ReadSFixed32)},
            {"sfixed64", nameof(ReadSFixed64)},
            {"bool", nameof(ReadBool)},
            {"string", nameof(ReadString)},
            {"bytes", nameof(ReadBytes)},
            {"float", nameof(ReadFloat)},
            {"double", nameof(ReadDouble)},
        };
        
        private static Dictionary<string, string> _writers = new Dictionary<string, string>
        {
            {"int32", nameof(WriteInt32)},
            {"int64", nameof(WriteInt64)},
            {"uint32", nameof(WriteUInt32)},
            {"uint64", nameof(WriteUInt64)},
            {"sint32", nameof(WriteSInt32)},
            {"sint64", nameof(WriteSInt64)},
            {"fixed32", nameof(WriteFixed32)},
            {"fixed64", nameof(WriteFixed64)},
            {"sfixed32", nameof(WriteSFixed32)},
            {"sfixed64", nameof(WriteSFixed64)},
            {"bool", nameof(WriteBool)},
            {"string", nameof(WriteString)},
            {"bytes", nameof(WriteBytes)},
            {"float", nameof(WriteFloat)},
            {"double", nameof(WriteDouble)},
        };
        
        public static void PrintBytes(byte[] bytes)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                sb.Append(bytes[i]);
                sb.Append(" ");
            }
            Debug.Log(sb.ToString());
        }
        
        public static string GetReadFuncName(string type)
        {
            return _readers[type];
        }
        
        public static string GetWriteFuncName(string type)
        {
            return _writers[type];
        }
        
        
        public static void ReadTag(byte[] data, ref int offset, out int fieldNum, out int wireType)
        {
            uint tagAndType = ReadRawVarint32(data, ref offset);
            fieldNum = (int)(tagAndType >> 3);
            wireType = (int)(tagAndType & 7);
        }
        
        public static int ReadTagInPlace(byte[] data, int offset, out int fieldNum, out int wireType)
        {
            uint tagAndType = ReadRawVarint32(data, ref offset);
            fieldNum = (int)(tagAndType >> 3);
            wireType = (int)(tagAndType & 7);
            return offset;
        }
        
        public static void ReadTag(byte[] data, ref int offset)
        {
            ReadRawVarint32(data, ref offset);
        }
        
        public static int ReadTagInPlace(byte[] data, int offset)
        {
            ReadRawVarint32(data, ref offset);
            return offset;
        }
        
        public static int ReadRawTag(byte[] data, int offset)
        {
            ReadRawVarint32(data, ref offset);
            return offset;
        }
        
        public static int ReadRawTagInPlace(byte[] data, int offset, out uint rawTag)
        {
            rawTag = ReadRawVarint32(data, ref offset);
            return offset;
        }

        
        public static byte[] WriteTag(int fieldNum, int wireType)
        {
            uint tagAndType = (uint)(fieldNum << 3 | wireType);
            return WriteRawVarint(tagAndType);
        }
        
        public static byte[] WriteTag(int fieldNum, string fieldType)
        {
            return WriteTag(fieldNum, GetWireType(fieldType));
        }
        
        public static uint CalcTag(int fieldNum, int wireType)
        {
            return (uint)(fieldNum << 3 | wireType);
        }
        
        public static uint CalcTag(int fieldNum, string fieldType)
        {
            return CalcTag(fieldNum, GetWireType(fieldType));
        }
        
        public static int GetWireType(string wireType)
        {
            switch (wireType)
            {
                case "int32":
                case "int64":
                case "uint32":
                case "uint64":
                case "sint32":
                case "sint64":
                case "bool":
                case "enum":
                case "fixed32":
                case "fixed64":
                case "sfixed32":
                case "sfixed64":
                    return WireType.Varint;
                case "string":
                case "bytes":
                    return WireType.LengthDelimited;
                case "float":
                    return WireType.Fixed32;
                case "double":
                    return WireType.Fixed64;
                default:
                    throw new InvalidDataException("Invalid wireType: " + wireType);
            }
        }
        
        public static int ReadLength(byte[] data, ref int offset)
        {
            return (int)ReadRawVarint32(data, ref offset);
        }
        
        public static byte[] WriteRawVarint(uint value)
        {
            MemoryStream stream = new MemoryStream();
            while (value > 127)
            {
                stream.WriteByte((byte)((value & 127) | 128));
                value >>= 7;
            }
            stream.WriteByte((byte)value);
            return stream.ToArray();
        }
        
        public static byte[] WriteRawVarint(ulong value)
        {
            MemoryStream stream = new MemoryStream();
            while (value > 127)
            {
                stream.WriteByte((byte)((value & 127) | 128));
                value >>= 7;
            }
            stream.WriteByte((byte)value);
            return stream.ToArray();
        }

        public static uint ReadRawVarint32(byte[] data, ref int offset)
        {
            uint value = 0;
            int shift = 0;
            if (data.Length <= offset)
            {
                return default;
            }
            while (shift < 32)
            {
                byte b = data[offset++];
                // Debug.Log("ReadRawVarint32:\t" + b);
                value |= (uint)(b & 127) << shift;
                if ((b & 128) == 0)
                    return value;
                shift += 7;
            }
            throw new InvalidDataException("Varint too long, the data is not valid");
        }
        
        public static ulong ReadRawVarint64(byte[] data, ref int offset)
        {
            ulong value = 0;
            int shift = 0;
            if (data.Length <= offset)
            {
                return default;
            }
            while (shift < 64)
            {
                byte b = data[offset++];
                // Debug.Log("ReadRawVarint64:\t" + b);
                value |= (ulong)(b & 127) << shift;
                if ((b & 128) == 0)
                    return value;
                shift += 7;
            }
            throw new InvalidDataException("Varint too long, the data is not valid");
        }
        
        public static byte[] WriteString(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return null;
            }
            byte[] stringBytes = Encoding.UTF8.GetBytes(value);
            byte[] lengthBytes = WriteRawVarint((uint)stringBytes.Length);
            return Combine(lengthBytes, stringBytes);
        }

        public static string ReadString(byte[] data, ref int offset)
        {
            int length = (int)ReadRawVarint32(data, ref offset);
            string value = Encoding.UTF8.GetString(data, offset, length);
            offset += length;
            return value;
        }

        private static byte[] Combine(byte[] first, byte[] second)
        {
            byte[] ret = new byte[first.Length + second.Length];
            Buffer.BlockCopy(first, 0, ret, 0, first.Length);
            Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
            return ret;
        }
        
        public static int ReadInt32(byte[] data, ref int offset)
        {
            return (int)ReadRawVarint32(data, ref offset);
        }

        public static long ReadInt64(byte[] data, ref int offset)
        {
            return (long)ReadRawVarint64(data, ref offset);
        }

        public static uint ReadUInt32(byte[] data, ref int offset)
        {
            return ReadRawVarint32(data, ref offset);
        }

        public static ulong ReadUInt64(byte[] data, ref int offset)
        {
            return ReadRawVarint64(data, ref offset);
        }

        public static int ReadSInt32(byte[] data, ref int offset)
        {
            uint value = ReadRawVarint32(data, ref offset);
            return (int)((value >> 1) ^ (-(value & 1)));
        }
        
        public static long ReadSInt64(byte[] data, ref int offset)
        {
            ulong value = ReadRawVarint64(data, ref offset);
            long decodedValue = (long)(value >> 1) ^ -(long)(value & 1);
            return decodedValue;
        }

        public static uint ReadFixed32(byte[] data, ref int offset)
        {
            uint value = BitConverter.ToUInt32(data, offset);
            offset += 4;
            return value;
        }

        public static ulong ReadFixed64(byte[] data, ref int offset)
        {
            ulong value = BitConverter.ToUInt64(data, offset);
            offset += 8;
            return value;
        }

        public static int ReadSFixed32(byte[] data, ref int offset)
        {
            int value = BitConverter.ToInt32(data, offset);
            offset += 4;
            return value;
        }

        public static long ReadSFixed64(byte[] data, ref int offset)
        {
            long value = BitConverter.ToInt64(data, offset);
            offset += 8;
            return value;
        }

        public static bool ReadBool(byte[] data, ref int offset)
        {
            return BitConverter.ToBoolean(data, offset++);
        }

        public static byte[] ReadBytes(byte[] data, ref int offset)
        {
            uint length = ReadRawVarint32(data, ref offset);
            if (length > data.Length - offset)
            {
                throw new ArgumentException("Invalid data, the length is too long. length: " + length + " offset: " + offset + " data.Length: " + data.Length);
            }
            byte[] value = new byte[length];
            Array.Copy(data, offset, value, 0, length);
            offset += (int)length;
            return value;
        }

        public static float ReadFloat(byte[] data, ref int offset)
        {
            float value = BitConverter.ToSingle(data, offset);
            offset += 4;
            return value;
        }

        public static double ReadDouble(byte[] data, ref int offset)
        {
            double value = BitConverter.ToDouble(data, offset);
            offset += 8;
            return value;
        }
        public static byte[] WriteInt32(int value)
        {
            return WriteRawVarint((uint)value);
        }

        public static byte[] WriteInt64(long value)
        {
            return WriteRawVarint((ulong)value);
        }

        public static byte[] WriteUInt32(uint value)
        {
            return WriteRawVarint(value);
        }

        public static byte[] WriteUInt64(ulong value)
        {
            return WriteRawVarint(value);
        }
        
        public static byte[] WriteSInt32(int value)
        {
            uint zigzagValue = (uint)((value << 1) ^ (value >> 31));
            return WriteRawVarint(zigzagValue);
        }
        
        public static byte[] WriteSInt64(long value)
        {
            ulong zigzagValue = (ulong)((value << 1) ^ (value >> 63));
            return WriteRawVarint(zigzagValue);
        }


        public static byte[] WriteFixed32(uint value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteFixed64(ulong value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteSFixed32(int value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteSFixed64(long value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteBool(bool value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteBytes(byte[] value)
        {
            if (value == null || value.Length == 0)
            {
                return null;
            }
            byte[] lengthBytes = WriteRawVarint((uint)value.Length);
            return Combine(lengthBytes, value);
        }

        public static byte[] WriteFloat(float value)
        {
            return BitConverter.GetBytes(value);
        }

        public static byte[] WriteDouble(double value)
        {
            return BitConverter.GetBytes(value);
        }
    }
}